/**
  ETFAna project, Anyang Normal University && IMP-CAS
  \class ETFVServer
  \brief client for communication among different hosts on the Internet
  NOTE that this is an abstract class
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2023-06-10
  \date 2023-06-10 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date         <th>Author      <th>Description                    </tr>
  <tr>  <td>2023-06-10   <td>Asia Sun    <td>file created                   </tr>
  </table>

  \copyright Copyright (c) 2021-2024 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <arpa/inet.h>
#include <errno.h>
#include <iostream>
#include <fcntl.h> // manipulate file descriptor
#include <unistd.h>
#include "ETFVServer.h"
#include "ETFReadShm.h"
#include "ETFQueue.h"
#include "ETFMsg.h"

using std::cout;
using std::endl;

#define er ETFMsg::Error

ETFVServer::ETFVServer() : ETFDaqData(), fds(-1), fdc(-1){}

/// create server with ip:port
ETFVServer::ETFVServer(const string &ip, short port, DaqType daqType)
: ETFDaqData(daqType), fIP(ip), fPort(port), fds(-1), fdc(-1), fShm0(0){}

ETFVServer::~ETFVServer(){
  Close();
} // end dtor

// estalbish the server and listen to linking requests
bool ETFVServer::Online(){
  if(!fShm0) return false; // just cannot do it

  // fds = socket(AF_INET, SOCK_STREAM, 0);
  fds = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // NON_BLOCK
  if(-1 == fds) er("ETFVServer", "Online: could not create socket: %s", strerror(errno));
  ETFMsg::Info("ETFVServer(%s)", "Online: Server socket created, fds: %d", fDaqC, fds);

  fSock.sin_family = AF_INET;
  fSock.sin_addr.s_addr = inet_addr(fIP.data());
  fSock.sin_port = htons(fPort); // host to net (short) byte order
  // the following lines before bind can avert 'Address already in use' error
  // after quick restart of the server
  int opt = 1;
  if(-1 == setsockopt(fds, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
    er("ETFVServer", "Online: setsockopt error: %s", strerror(errno));
  const int res = bind(fds, (sockaddr *)&fSock, sizeof(fSock));
  if(-1 == res) er("ETFVServer", "Online: socket bind error: %s", strerror(errno));
  ETFMsg::Info("ETFVServer(%s)", "Online: Server socket bound to %s:%d",
    fDaqC, fIP.data(), fPort);

  // FIXME: but for the time being we only make connection with only 1 client //
  listen(fds, kMAXCLI);

  socklen_t clen = sizeof(sockaddr_in);
  sockaddr_in client; // for the socket to link

  // time out waiting for a connection of a client //
  static const int DT = 600000; // in ms
  time_t t0 = ETFMsg::ms(), t1 = t0, dt = DT;
  ETFMsg::Info("ETFVServer(%s)", "Online: Stand by for incoming linking...", fDaqC);
  while(-1 == (fdc = accept4(fds, (sockaddr *)&client, &clen, SOCK_NONBLOCK))){
    if(ETFMsg::ms() - t1 >= 1000){ // print every second
      cout << "About to time out: " << dt/1000 << "s\r" << std::flush;
      dt -= 1000; t1 = ETFMsg::ms();
    } // end if
    if(ETFMsg::ms() - t0 > DT){
      ETFMsg::Info("ETFVserver(%s)", "Online: connection timed out: %ds", fDaqC, DT/1000);
      exit(-1);
    } // end if
    if(ETFMsg::irp()) return false;
  } // end while
  ETFMsg::Info("ETFVServer(%s)", "Online: get a client, fdc: %d", fDaqC, fdc);

  // send connction info to client to acknowledge the connection //
  char m0[128] = "";
  sprintf(m0, "   --Client ip:port is \033[32;1m%s:%d\033[0m. %s connection established.",
    inet_ntoa(client.sin_addr), ntohs(client.sin_port), fDaqC);
  puts(m0);
  // print shm status //
  cout << "\033[35;1m_______nstored: " << fShm0->nstored();
  cout << " nempty: " << fShm0->nempty() << "________\033[0m" << endl;
  ssize_t s = 0, p = 0;
  while(1){ // unblock read
    s = write(fdc, m0 + p, sizeof(m0) - p);
    if(-1 == s && EAGAIN == errno) continue;
    if(s > 0) p += s;
    if(size_t(p) <= sizeof(m0)) break;
  } // end while
  if(-1 == s) er("ETFVServer", "Online: writing ack error: %s", strerror(errno));

  return true;
} // end member function Online

// clean-ups
void ETFVServer::Close(){
  if(fds >= 0){ close(fds); fds = -1; }
  if(fdc >= 0){ close(fdc); fdc = -1; }
  if(fShm0){ delete fShm0; fShm0 = nullptr; }

  printf("\033[36;1mThis is %s server. Bye~\033[0m\n", fDaqC);
} // end member function Close

void ETFVServer::Initialize(){
  if(fShm0) fShm0->Initialize(); // receive data from the daq's shm
} // end member function Initialize
